package entities;

import entities.exceptions.EmptyRouteException;
import entities.exceptions.MaxFactoryCapacityExceededException;
import entities.exceptions.MissingRouteException;
import entities.exceptions.NotEnoughSuppliesToMakeRobotException;
import entities.exceptions.NotEnoughSupplyException;
import entities.exceptions.RouteAlreadyExistsException;
import entities.exceptions.WrongFactoryException;
import entities.exceptions.WrongSupplyException;
import graphics.WindowManager;
import java.awt.Image;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;
import willispace.Config;
import willispace.Log;
import willispace.WilliSpace;

/**
 * Factory class. Manages acces to factory, building robots, local supplies.
 * @author mateusz
 */
public class Factory extends Entity {

    /**
     * extracted supply
     */
    protected Supplies supply;
    /**
     * diamond units
     */
    protected volatile int diamondSupply;
    /**
     * gas units
     */
    protected volatile int gasSupply;
    private final int workerCost;
    private final int transporterCost;
    /**
     * tile assosiated with this factory
     */
    protected Tile associatedTile;
    /**
     * list of routes to supplies
     */
    protected LinkedList<Route> suppliesRoutes;
    /**
     * list of routes to factories
     */
    protected LinkedList<Route> factoriesRoutes;
    /**
     * list of created workers
     */
    protected volatile LinkedList<Worker> workers;
    /**
     * list of created transporters
     */
    protected volatile LinkedList<Transporter> transporters;
    private Random randomGenerator;
    /**
     * max capacity of factory
     */
    protected int maxCapatiy;

    /**
     * Creates factory
     * @param t         associtated tile
     * @param img       image
     * @param supply    extracted supply
     * @param myName    name of entity
     */
    public Factory(Tile t, Image img, Supplies supply, String myName) {
        super(t.getXY(), img, WindowManager.fullScreenWindow.middlePanel, myName);

        this.supply = supply;
        this.associatedTile = t;
        
        randomGenerator = new Random();
        
        
        suppliesRoutes = new LinkedList<Route>();
        factoriesRoutes = new LinkedList<Route>();
        workers = new LinkedList<Worker>();
        transporters = new LinkedList<Transporter>();

        
        // robots build cost
        workerCost = randomGenerator.nextInt(Config.maxWorkerCost - 1) + 1;
        transporterCost = randomGenerator.nextInt(Config.maxTransporterCost - 1) + 1;

        maxCapatiy = randomGenerator.nextInt(Config.maxFactoryCapactity-Config.minFactoryCapacity)+Config.minFactoryCapacity;
        
        
        // start state, allowing to create two workers and transporters
        gasSupply = diamondSupply = (workerCost + transporterCost)*2;

        updateProperties();
    }

    @Override
    protected void updateProperties() {
        super.updateProperties();
        properties.put("supply", getExtractedSupply().toString());
        properties.put("diamondSupply", Integer.toString(diamondSupply));
        properties.put("gasSupply", Integer.toString(gasSupply));
        properties.put("suppliesRoutes", Integer.toString(suppliesRoutes.size()));
        properties.put("factoriesRoutes", Integer.toString(factoriesRoutes.size()));
        properties.put("workers", Integer.toString(workers.size()));
        properties.put("transporters", Integer.toString(transporters.size()));
        properties.put("totalCapacity", Integer.toString(maxCapatiy));
    }

    /**
     * Destroy factory. Destroys also created by this factory robots.
     */
    public synchronized void destroy() {
        associatedTile.unassociateWithEntity();
        removeFromProperPanel();

        // usun drogi
        for (Route r : suppliesRoutes) {
            deleteRoute(r, suppliesRoutes);
        }

        for (Route r : factoriesRoutes) {
            deleteRoute(r, factoriesRoutes);
        }

        // usun robotników
        for (Worker w : workers) {
            WilliSpace.destroyRobot(w);
        }

        // usun transporterów
        for (Transporter tr : transporters) {
            WilliSpace.destroyRobot(tr);
        }

    }
    // ============
    // WORKER STUFF
    // ============
    /**
     * Creates new worker
     * @throws MissingRouteException                    if factory hasn't route to another factory or supply
     * @throws NotEnoughSuppliesToMakeRobotException    if factory has not enough supplies to make robot
     */
    public synchronized void newWorker() throws MissingRouteException, NotEnoughSuppliesToMakeRobotException {

        if (!readyToCreateRobots()) {
            throw new MissingRouteException();
        }

        try {
            getUnitsFromFactory(getWorkerCost());
        } catch (NotEnoughSupplyException ex) {
            throw new NotEnoughSuppliesToMakeRobotException();
        }


        Worker worker = WilliSpace.makeWorker(this);
        workers.add(worker);

        newRouteForWorker(worker);

        updateProperties();
    }

    /**
     * Removes worker from list
     * @param worker    worker to remove
     * @return          Cost of workers build.
     */
    public synchronized int deleteWorker(Worker worker) {
        workers.remove(worker);
        return getWorkerCost();
    }

    /**
     * Adds new route for worker
     * @param worker                    worker who wants new route
     * @throws MissingRouteException    If factory hasn't any route to supply
     */
    public void newRouteForWorker(Worker worker) throws MissingRouteException {
        Route workerRoute = getRandomRouteToSupply();
        worker.addRoute(workerRoute);
    }

    /**
     * Change workers route
     * @param w                         worker who wants new route
     * @param s                         target supply
     * @throws MissingRouteException    if factory hasn't route to that supply
     * @throws WrongSupplyException     if type of supply mismatch type of extracted supply
     */
    public void changeWorkerRoute(Worker w, Supply s) throws MissingRouteException, WrongSupplyException {
        // if types of supplies mismatch
        if (s.getType() != this.supply) {
            throw new WrongSupplyException();
        }

        Route r = findRouteToSupply(s);

        w.addRoute(r);
    }

    // ============
    // TRANSPORTER STUFF
    // ============
    /**
     * Creates new transporter
     * @throws MissingRouteException                    if factory hasn't route to another factory or supply
     * @throws NotEnoughSuppliesToMakeRobotException    if factory has not enough supplies to make robot
     */
    public synchronized void newTransporter() throws MissingRouteException, NotEnoughSuppliesToMakeRobotException {
        if (!readyToCreateRobots()) {
            throw new MissingRouteException();
        }

        try {
            getUnitsFromFactory(getTransporterCost());
        } catch (NotEnoughSupplyException ex) {
            throw new NotEnoughSuppliesToMakeRobotException();
        }


        Route randomRoute = getRandomRouteToFactory();

        Transporter transporter = WilliSpace.makeTransporter(this, randomRoute);
        transporters.add(transporter);
        
        updateProperties();
    }

    /**
     * Removes transporter
     * @param transporter   transporter to remove
     * @return              build cost
     */
    public synchronized int deleteTransporter(Transporter transporter) {
        transporters.remove(transporter);
        return getTransporterCost();
    }

    // dodaje nowy losowy cel dla transportera
    /**
     * Adds new random target for transporter
     * @param transporter               transporter who want new target
     * @throws MissingRouteException    if factory hasn't any route to other factory
     */
    public void newRouteForTransporter(Transporter transporter) throws MissingRouteException {
        Route randomRoute = getRandomRouteToFactory();
        transporter.addRoute(randomRoute);
    }

    // dodaje droge do fabyrki f dla transportera t
    /**
     * Adds route to factory f for transporter
     * @param t                         transporter who wants the next target to be f
     * @param f                         factory target
     * @throws MissingRouteException    if factory hasn't route to that factory
     */
    public void addTransporterTarget(Transporter t, Factory f) throws MissingRouteException {
        Route transporterRoute = findRouteToFactory(f);
        t.addRoute(transporterRoute);
    }

    // ============
    // ROUTES STUFF
    // ============
    // finding routes
    /**
     * Finds route to supply
     * @param   s supply to be founded
     * @return  route to supply s
     * @throws MissingRouteException    if factory hasn't route to that supply
     */
    protected Route findRouteToSupply(Supply s) throws MissingRouteException {
        return findRoute(s, suppliesRoutes);
    }

    /**
     * Finds route to factory
     * @param f     factory to be founded
     * @return      route to factory f
     * @throws MissingRouteException    if factory hasn't route to that supply
     */
    protected Route findRouteToFactory(Factory f) throws MissingRouteException {
        return findRoute(f, factoriesRoutes);
    }

    /**
     * Finds route to entity e on specified list
     * @param e             entity to be founded
     * @param routesList    list of routes
     * @return              route to entity
     * @throws MissingRouteException    if factory hasn't route to this entity
     */
    protected Route findRoute(Entity e, LinkedList routesList) throws MissingRouteException {
        Iterator it = routesList.iterator();
        while (it.hasNext()) {
            Route r = (Route) it.next();
            try {
                if (((e instanceof Factory) && (Factory) r.getEnd().getAssociatedEntity() == (Factory) e)
                        || ((e instanceof Supply) && (Supply) r.getEnd().getAssociatedEntity() == (Supply) e)) {
                    return r;
                }
            } catch (EmptyRouteException ex) {
            }
        }
        throw new MissingRouteException();
    }

    
    /**
     * Adds route to supply. Notifies all robots that new route was added.
     * @param supply                supply target
     * @throws WrongSupplyException         if type of supply mismatches type of extracted supply
     * @throws RouteAlreadyExistsException  if route already exists
     */
    public synchronized void addRouteToSupply(Supply supply) throws WrongSupplyException, RouteAlreadyExistsException {
        Entity e = supply;

        if ((e instanceof Gas && this.getExtractedSupply() != Supplies.GAS) || (e instanceof Diamond && this.getExtractedSupply() != Supplies.DIAMOND)) {
            throw new WrongSupplyException();
        }
        try {
            findRouteToSupply(supply);
            throw new RouteAlreadyExistsException();
        } catch (MissingRouteException ex) {
        }

        Route route = new Route(this.getAssociatedTile(), supply.getAssociatedTile());

        suppliesRoutes.add(route);
        updateProperties();

        WilliSpace.notifyAllRobots();
    }

    /**
     * Adds route to factory. Notifies all robots that new route was added.
     * @param factory                       factory target
     * @throws WrongFactoryException        if route should lead from factory to itself
     * @throws RouteAlreadyExistsException  if route already exists
     */
    public synchronized void addRouteToFactory(Factory factory) throws RouteAlreadyExistsException, WrongFactoryException {
        if (factory == this) {
            throw new WrongFactoryException();
        }

        try {
            findRouteToFactory(factory);
            throw new RouteAlreadyExistsException();
        } catch (MissingRouteException ex) {
        }

        Route route = new Route(this.getAssociatedTile(), factory.getAssociatedTile());

        factoriesRoutes.add(route);
        updateProperties();
    }

    /**
     * Gets random route to supply
     * @return  random route to supply
     * @throws MissingRouteException    if factory hasn't any route to supply
     */
    protected Route getRandomRouteToSupply() throws MissingRouteException {
        return getRandomRoute(suppliesRoutes);
    }

    /**
     * Gets random route to factory
     * @return  random route to factory
     * @throws MissingRouteException    if factory hasn't any route to another factory
     */
    protected Route getRandomRouteToFactory() throws MissingRouteException {
        return getRandomRoute(factoriesRoutes);
    }

    /**
     * Gets random route from specified list
     * @param routesList    list of routes to by analyzed
     * @return              random route from list
     * @throws MissingRouteException    if list is empty
     */
    protected Route getRandomRoute(LinkedList<Route> routesList) throws MissingRouteException {
        if (routesList.isEmpty()) {
            throw new MissingRouteException();
        }
        int index = randomGenerator.nextInt(routesList.size());
        Route route = routesList.get(index);
        return route;
    }

    /**
     * Delete route to supply
     * @param s supply target
     * @throws MissingRouteException    if factory hasn't route to that supply
     */
    public synchronized void deleteRouteToSupply(Supply s) throws MissingRouteException {
        Route r = findRouteToSupply(s);
        deleteRoute(r, suppliesRoutes);
    }

    /**
     * Deletes route to factory
     * @param f factory target
     * @throws MissingRouteException    if factory hasn't route to that factory
     */
    public synchronized void deleteRouteToFactory(Factory f) throws MissingRouteException {
        Route r = findRouteToFactory(f);
        deleteRoute(r, factoriesRoutes);
    }

    /**
     * Delete route
     * @param r route to be deleted
     */
    protected synchronized void deleteRoute(Route r, LinkedList<Route> list) {
        r.destroy();
        list.remove(r);
        updateProperties();
    }

    /**
     * Check if factory is ready to create robots
     * @return true if has at least one route to another supply and factory
     */
    public boolean readyToCreateRobots() {
        return suppliesRoutes.size() > 0 && factoriesRoutes.size() > 0;
    }

    // ============
    // SUPPLIES STUFF
    // ============
    /**
     * Takes units from robot
     * @param units units to take
     * @throws MaxFactoryCapacityExceededException  if max capacity of factory will be exceeded
     */
    public synchronized void addUnits(int units) throws MaxFactoryCapacityExceededException {
        addUnits(units, this.supply);
    }

    // bierze jednostki i dodaje do właściwego zasobu
    // TO DO - sprawdzanie czy jest pełny
    /**
     * Takes units of supply from robot
     * @param units     units to take
     * @param supply    supply type
     * @throws MaxFactoryCapacityExceededException  if max capacity of factory will be exceeded
     */
    public synchronized void addUnits(int units, Supplies supply) throws MaxFactoryCapacityExceededException {
        if (supply == Supplies.DIAMOND) {
            if(this.diamondSupply + units > this.maxCapatiy){
                throw new MaxFactoryCapacityExceededException();
            }
            this.diamondSupply += units;
        } else if (supply == Supplies.GAS) {
            if(this.gasSupply + units > this.maxCapatiy){
                throw new MaxFactoryCapacityExceededException();
            }
            this.gasSupply += units;
        }

        updateProperties();

        // wake up all robots waiting for supplies to run
        WilliSpace.notifyAllRobots();
    }

    /**
     * Get from all local supplies some units
     * @param units                     units to be taken
     * @throws NotEnoughSupplyException if factory hasn't enough local supplies
     */
    public synchronized void getUnitsFromFactory(int units) throws NotEnoughSupplyException {
        if (diamondSupply < units || gasSupply < units) {
            throw new NotEnoughSupplyException();
        }
        diamondSupply -= units;
        gasSupply -= units;
        
        updateProperties();
        // wake up all robots waiting for empty space to run
        WilliSpace.notifyAllRobots();
   }

    // pobiera units jednostek z zasobu supply
    /**
     * Gets from specified local supply some units
     * @param units                     units to be taken
     * @param supply                    supply type
     * @throws NotEnoughSupplyException if factory hasn't enough local supplies
     */
    public synchronized void getUnitsFromFactory(int units, Supplies supply) throws NotEnoughSupplyException {
        int givenUnits = 0;
        if (supply == Supplies.DIAMOND) {
            if (diamondSupply < units) {
                throw new NotEnoughSupplyException();
            }
            diamondSupply -= units;
        } else if (supply == Supplies.GAS) {
            if (gasSupply < units) {
                throw new NotEnoughSupplyException();
            }
            gasSupply -= units;
        }
        updateProperties();
        
        // wake up all robots waiting for empty space to run
        WilliSpace.notifyAllRobots();
    }

    /**
     * 
     * @return gas units in factory
     */
    public int getGasUnits() {
        return gasSupply;
    }

    /**
     * 
     * @return diamonds units in factory
     */
    public int getDiamondSupply() {
        return diamondSupply;
    }

    /**
     * @return the associatedTile
     */
    public Tile getAssociatedTile() {
        return associatedTile;
    }

    /**
     * @return the supply
     */
    public Supplies getExtractedSupply() {
        return supply;
    }

    /**
     * @return the workerCost
     */
    public int getWorkerCost() {
        return workerCost;
    }

    /**
     * @return the transporterCost
     */
    public int getTransporterCost() {
        return transporterCost;
    }
}
